
package com.eva.framework;

import com.eva.epc.common.util.CommonUtils;
import com.eva.framework.dto.DataFromClient;
import com.eva.framework.dto.DataFromServer;
import com.eva.framework.dto.SysProcessorConst;
import com.eva.framework.utils.LoggerFactory;
import com.ys.chatserver.common.dto.cnst.MyProcessorConst;
import com.ys.chatserver.common.dto.cnst.RetResult;
import com.ys.chatserver.http.logic.LogicProcessor2;
import com.ys.chatserver.http.logic.filter.JwtHelper;
import com.ys.chatserver.http.logic.filter.JwtParam;
import com.ys.chatserver.tool.EmptyUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Vector;

/**
 * 服务端http框架的MVC控制器父类，用于捕获客客户端的http请求并调
 * 度业务处理器（Processor），遵从MVC框架设计原则。
 * <p>
 * <b>MVC调用层级: </b>
 * {@link #service(HttpServletRequest, HttpServletResponse)}
 * -> {@link #process(HttpServletRequest, HttpServletResponse)}
 * -> {@link #dispatch(DataFromClient, HttpServletRequest, HttpServletResponse)}.
 * <p>
 * <b>MVC调用原理: </b>
 * <pre>
 * -------------------------------------------- MVC框架的HTTP处理逻辑流程 ----------------------------------------------
 * http请求 => HttpPortal子类(MVC控制器) -> Processor子类(处理器) -> JobDispatcher方法(作业调度器) -> Action代码段(动作)
 *                                               |                        |                                |
 *                                         [processor_id]         [job_dispatch_id]                   [action_id]
 * --------------------------------------------------------------------------------------------------------------------
 * </pre>
 *
 * @author Jack Jiang(http://www.52im.net/space-uid-1.html)
 * @version 1.0
 */
public abstract class HttpController extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * 本项目部置到服务端上去后的物理磁盘根目录
     */
    public static String SERVER_PATH = "";

    /**
     * 本Servlet实例化时的初始化方法.
     *
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        SERVER_PATH = config.getServletContext().getRealPath("/");
        super.init(config);
        LoggerFactory.getLog().info("Hello EVA.EPC,init ok... ");
    }

    /**
     * 响应客端请求的主方法(本方法由Web容器调用).
     *
     * @param req HttpServletRequest
     * @see HttpServlet
     */
    @Override
    public void service(HttpServletRequest req, HttpServletResponse resp) {
        try {
            req.setCharacterEncoding("UTF-8"); // 处理post乱码
            resp.setContentType("application/json; charset=utf-8"); // 响应内容的类型
            //        设置响应头允许ajax跨域访问
            resp.setHeader("Access-Control-Allow-Credentials", "true");
            resp.setHeader("Access-Control-Allow-Origin", "*"); // 允许所有的ip的可以访问
            resp.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); // 允许访问的方式
            resp.setHeader("Access-Control-Max-Age", "3600");  // 设置超时时间
            resp.setHeader("Access-Control-Allow-Headers", "token, Accept, Origin, X-Requested-With, Content-Type, Last-Modified");
            //调用核心处理方法
            process(req, resp);
        } catch (Exception e) {
//			e.printStackTrace();
//			throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 核心处理方法.<br>
     * <p>
     * 本方法会确保处理完成后写回一个DataFromServer对象（客户端通过它可知是否处理成功等信息）给客端，如果处理成功则
     * DataFromServer对象中的sucess字值会被设置成true且returnValue会设置成处理完成的返回的对象，否则sucess将被设置成
     * false且returnValue里存放的将是错误消息文本（该文本不能保证一定不是null）.
     * </p>
     * <pre>
     * 	注：子类的处理实现推荐overload方法processImpl(DataFromClient, HttpServletRequest, HttpServletResponse)
     * ，一般情况下不推荐重写本方法.
     * </pre>
     *
     * @param req HttpServletRequest 客户端请求HttpServletRequest对象
     * @param res HttpServletResponse 客户端响应HttpServletResponse对象
     * @throws Exception 本方法调用中出现任何异常时都会抛出
     * @see DataFromServer
     */
    protected void process(HttpServletRequest req, HttpServletResponse res)
            throws Exception {
        //服务端是否要写回数据给客户端（本字段对应于DataFromClient中的doInput字段并且意义一样）
        boolean needRecieve = true;

        //处理完成后将要返回给客端的数据对象（通过它可知是否处理成功等信息）
        DataFromServer objFromServer = new DataFromServer();
        try {
            LoggerFactory.getLog().debug(">>S 客户端Cookie=" + req.getRequestedSessionId()
                    + " ip=" + req.getRemoteHost() + ":" + req.getRemotePort());
            //第1/3步：读取PC客户端发过来的数据
            DataFromClient dataFromClient = recieveFromClient(req);
            //* 服务端是否要写回数据给客户端
            needRecieve = dataFromClient.isDoInput();
            //boolean b = tokenAuth(dataFromClient, objFromServer);
            if (true) {
                //第2/3步：提交处理器进行业务处理
                Object processedData = dispatch(dataFromClient, req, res);
                //处理成功了
                objFromServer.setSuccess(true);
                objFromServer.setReturnValue(processedData);
            }
        } catch (Exception e) {
            objFromServer.setSuccess(false);
//			objFromServer.setReturnValue("在方法process(..)中出错,原因是:"+e.getMessage());
            // 2014-05-05日邮js改成只返回简单错误信息，而非完整Exception对象，目的是在跨平台实现时反射成JSON时不会StackOverflowError
            objFromServer.setReturnValue(CommonUtils.getExceptionStackTraceForPlainText(e));//e);
//			e.printStackTrace();
            LoggerFactory.getLog().warn(e.getMessage(), e);
            throw e;
        } finally {
            //* 服务端需要写回数据给客户端
            if (needRecieve) {
                //第3/3步：将处理完成的数据对象发回给客户端
                sendToClient(res, objFromServer);
            }
            LoggerFactory.getLog().debug(">>E 处理完成,客户端" + " ip=" + req.getRemoteHost() + ":" + req.getRemotePort()
                    + " 的请处理成功了?" + objFromServer.isSuccess() + (needRecieve ?
                    " - ret=" + objFromServer.getReturnValue().getClass()//objFromServer.getReturnValue() 直接输出返回值用于早期MVC框架调试时，生产环境下将大幅影响系统性能
                    : " - [doInput=false,无需返回值给客户端]"));
        }
    }

    /**
     * token验证
     */
    private boolean tokenAuth(DataFromClient dfc, DataFromServer objFromServer) throws Exception {
        boolean authSuccess = true;
        int processor_id = dfc.getProcessorId();
        //token校验
        if (processor_id != MyProcessorConst.PROCESSOR_LOGIN_4ALL &&
                processor_id != MyProcessorConst.PROCESSOR_CHECK_UPDATE_4A
                && processor_id != MyProcessorConst.PROCESSOR_LOGIN$CHECKUPDATE_4A
                && processor_id != SysProcessorConst.PROCESSSOR_LOGOUT) {
            if (dfc.getToken() == null) {
                objFromServer.setSuccess(false);
                objFromServer.setReturnValue(RetResult.failResult("没有找到该用户!"));
                authSuccess = false;
            } else {
                JwtParam jwtParam = null;
                try {
                    jwtParam = JwtHelper.parseJwt(dfc.getToken());
                } catch (IOException e) {
                    objFromServer.setSuccess(false);
                    objFromServer.setReturnValue(RetResult.failResult("无效的token!"));
                    authSuccess = false;
                }
                String userId = jwtParam.getUserId();
                String sql = "select latest_login_time2 from g_users where del_flag = 0 and user_uid = '" + userId + "'";
                Vector<Vector<?>> vectors = LogicProcessor2.db.queryData(sql);
                if (vectors.isEmpty()) {
                    objFromServer.setSuccess(false);
                    objFromServer.setReturnValue(RetResult.failResult("没有找到该用户!"));
                    authSuccess = false;
                } else {
                    String string = vectors.get(0).get(0).toString();
                    if (EmptyUtils.isEmpty(string)) {
                        objFromServer.setSuccess(false);
                        objFromServer.setReturnValue(RetResult.failResult("认证过期!"));
                        authSuccess = false;
                    } else {
                        long loginTime = Long.parseLong(string);
                        if (jwtParam.getLoginTime().getTime() != loginTime) {
                            objFromServer.setSuccess(false);
                            objFromServer.setReturnValue(RetResult.failResult("认证过期!"));
                            authSuccess = false;
                        }
                    }
                }
            }
        }
        return authSuccess;
    }


    /**
     * 将数据对象发回给PC客户端.
     *
     * @param res           客户端响应HttpServletResponse对象
     * @param objFromServer 要发送的可系列化的数据对象
     * @throws Exception 如果在操作OutputStream时出现异常
     */
    protected void sendToClient(HttpServletResponse res, DataFromServer objFromServer)
            throws IOException {
        ObjectOutputStream objOut = new ObjectOutputStream(res.getOutputStream());
        objOut.writeObject(objFromServer);
        objOut.flush();
        objOut.close();
    }

    /**
     * 读取PC客户端传过来的数据对象。
     *
     * @param req 客户端请求HttpServletRequest对象
     * @return 返回读取出来的客户端数据对象
     * @throws IOException            如果req.getInputStream()出现异常时抛出
     * @throws ClassNotFoundException 如果is.readObject()出现异常时抛出
     */
    protected DataFromClient recieveFromClient(HttpServletRequest req)
            throws IOException, ClassNotFoundException {
        ObjectInputStream is = new ObjectInputStream(req.getInputStream());
        DataFromClient ObjFromClient = (DataFromClient) is.readObject();
        is.close();
        return ObjFromClient;
    }

    /**
     * 客户端的请求处理实现.
     * <pre>
     * 	注：子类一般情况只需要重写此类即可达到要求。
     * </pre>
     *
     * @param objFromClient 这就是客户传过来的数据对象了（本方法中是必须的参数）
     * @param req           客户端请求实例引用（把它传过来完全是未了考虑以后可能有其它作用，本方法中它不是必须的）
     * @param res           客户端响应实例引用（把它传过来完全是未了考虑以后可能有其它作用，本方法中它不是必须的）
     * @return 处理完返回的对象（要给客户端的）
     * @throws Exception 本方法调用中出现任何异常时都会抛出
     */
    protected abstract Object dispatch(DataFromClient objFromClient, HttpServletRequest req
            , HttpServletResponse res) throws Exception;
}
